<!DOCTYPE html>
<html class="no-js" lang="zh">
  <head>
<meta charset="utf-8">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<link rel="alternate" href="https://www.desgard.com" hreflang="pt-BR">
<link href="http://gmpg.org/xfn/11" rel="profile">
<meta name="HandheldFriendly" content="True">
<meta name="MobileOptimized" content="320">
<meta name="theme-color" content="#005344">
<title>Guardia · 瓜地</title>
<meta name="google-site-verification" content="zS1dSn20XtA4FJYEOQLXqI0boxZdMnJ2g3beje-cl20">
<meta name="description" content="I write many code to write less code.💻">
<meta name="keywords" content="">
<!-- Social: Facebook / Open Graph -->
<meta property="og:url" content="https://www.desgard.com/fishhook-1/">
<meta property="og:title" content="       巧用符号表 - 探求 fishhook 原理（一） | Gua  ">
<meta property="og:description" content="I write many code to write less code.💻">
<meta property="og:site_name" content="Desgard_Duan">
<meta property="og:locale" content="pt_BR">
<meta property="og:type" content="website">
<meta property="og:author" content="https://www.facebook.com/desgard.duan">
<meta property="og:image" content="https://www.desgard.com">
<!-- Social: Twitter -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@nandomoreirame">
<meta name="twitter:domain" content="https://www.desgard.com">
<meta name="twitter:title" content="       巧用符号表 - 探求 fishhook 原理（一） | Gua  ">
<meta name="twitter:description" content="I write many code to write less code.💻">
<meta name="twitter:image:src" content="https://www.desgard.com">
<!-- Favicons -->
<link rel="apple-touch-icon" sizes="114x114" href="https://www.desgard.com/assets/ico/apple-touch-icon-114-516f4e19976b9e4dbb77ad9b576831fe.png">
<link rel="apple-touch-icon" sizes="72x72" href="https://www.desgard.com/assets/ico/apple-touch-icon-72-5409b2df229305703caf583d86c845ab.png">
<link rel="apple-touch-icon" href="https://www.desgard.com/assets/ico/apple-touch-icon-57-aa873e019cf659e0d4e6a0b5bb9f379d.png">
<link rel="shortcut icon" href="https://www.desgard.com/assets/ico/favicon-4298be3d3fbe23e18d65bded9d930899.png">
<!-- rel prev and next -->
<!-- Canonical link tag -->
<link rel="canonical" href="https://www.desgard.com/fishhook-1/">
<link rel="alternate" type="application/rss+xml" title="Gua" href="https://www.desgard.com/feed.xml">
<link rel="stylesheet" href="/assets/main-0b7b828712f4c43b75bba4861c907bb1.css">
<script src="/assets/modernizr-custom-cb807611a3e262b2eac59444cbab74d6.js" data-cfasync="false"></script>
<script src="/assets/js/jquery-3.1.js"></script>
<script src="/assets/js/jquery.rotate.js"></script>
<script type="text/javascript" src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<script type="application/ld+json">
{
  "@context": "http://schema.org",
  "@type": "Website",
  "publisher": "www.desgard.com",
  "url": "http://www.desgard.com/",
  "description": "瓜地"
}
</script>
<script type="text/javascript">
  var disqus_shortname = 'desgard',
      baseurl          = '';
</script>
<!--
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-52446115-1']);
_gaq.push(['_trackPageview']);
(function() {
  var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
  ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
  var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>-->
<script>
var _hmt = _hmt || [];
(function() {
  var hm = document.createElement("script");
  hm.src = "//hm.baidu.com/hm.js?c5a8123bc51782a3083a631ed9ff50e4";
  var s = document.getElementsByTagName("script")[0]; 
  s.parentNode.insertBefore(hm, s);
})();
</script>
<script type="text/x-mathjax-config">
    MathJax.Hub.Config({
      tex2jax: {
        inlineMath: [ ['$','$'] ],
        displayMath: [ ['$$','$$'] ],
        processEscapes: true
      }
    });
  </script>
  </head>
  <body class="post-template single">
    <header class="header">
  <div class="container">
    <h1><a href="/"><strong>desgard</strong>.com</a></h1>
    <nav class="navbar">
      <ul>
        <li><a href="/">Home</a></li>
        <li><a href="#!" data-modal="modalSearch"><i class="fa fa-search"></i></a></li>
        <li><a href="/comment " target="_blank"><i class="fa fa-comments"></i></a></li>
        <li><a href="/feed.xml" target="_blank"><i class="fa fa-feed"></i></a></li>
      </ul>
    </nav>
  </div>
</header>
    <main class="wrapper container" itemprop="mainContentOfPage" itemscope="itemscope" itemtype="http://schema.org/Blog">
      <article class="post" itemscope="itemscope" itemtype="http://schema.org/BlogPosting" itemprop="blogPost">
  <header class="post-header">
    <h1>巧用符号表 - 探求 fishhook 原理（一）</h1>
    <div class="post-meta">
      <time datetime="2015-10-13">
        <i class="fa fa-calendar-o"></i> <time datetime="2017-12-17"> 2017-12-17
      </time>
      <span>
        <i class="fa fa-comments"></i> <a href="#container">Comment me!</a>
      </span>
      <span>
<!--
        <i class="fa fa-folder-open-o"></i> 
-->
      </span>
    </div>
  </header>
  <blockquote>
  <p>这是 <em>探求 fishhook 原理</em> 系列的第一篇。主要讲述了 Facebook 开源库 <a href="">fishhook</a> 的源码实现细节。需要具备 Mach-O 相关知识。可以先阅读 <em><a href="http://www.desgard.com/iosre-1/">Mach-O 文件格式探索</a></em> 一文。</p>
</blockquote>
<h2 id="section">关于符号表的基本知识</h2>
<h3 id="lazy-binding-">Lazy Binding 过程</h3>
<p>在之前的<a href="http://www.desgard.com/iosre-1/">Mach-O 文件格式探索</a>一文中提及到了 <code>__DATA.__la_symbol_ptr</code> 和 <code>__DATA.__nl_symbol_ptr</code> 这两个指针表，分别为<strong>lazy binding指针表</strong>和<strong>non lazy binding指针表</strong>。并且这两个指针表，保存着与字符串标对应的函数指针。</p>
<p>而 Mach-O 文件文件中通过 dyld 加载的 Lazy Binding 表并没有在加载过程中直接确定地址列表，而是在第一次调用该函数的时候，通过 <strong>PLT(Procedure Linkage Table)</strong> 来进行一次 Lazy Binding。来使用 <code>printf</code> 方法验证一下：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1">#include &lt;stdio.h&gt;</span>
<span class="n">int</span> <span class="n">main</span><span class="p">(</span><span class="n">int</span> <span class="n">argc</span><span class="p">,</span> <span class="n">const</span> <span class="n">char</span> <span class="o">*</span> <span class="n">argv</span><span class="o">[]</span><span class="p">)</span> <span class="p">{</span>
    <span class="nb">printf</span><span class="p">(</span><span class="s2">&quot;%s</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">,</span> <span class="s2">&quot;hello world&quot;</span><span class="p">);</span>
    <span class="nb">printf</span><span class="p">(</span><span class="s2">&quot;%s</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">,</span> <span class="s2">&quot;hello desgard&quot;</span><span class="p">);</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span></code></pre></div>
<p>拖到 <em>Hopper</em> 中查看汇编：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="ss">_main</span><span class="p">:</span>
<span class="n">push</span>       <span class="n">rbp</span>
<span class="n">mov</span>        <span class="n">rbp</span><span class="p">,</span> <span class="n">rsp</span>
<span class="nb">sub</span>        <span class="n">rsp</span><span class="p">,</span> <span class="mh">0x10</span>
<span class="n">lea</span>        <span class="n">rdi</span><span class="p">,</span> <span class="n">qword</span> <span class="o">[</span><span class="mh">0x100000f8e</span><span class="o">]</span> <span class="p">;</span> <span class="s2">&quot;hello world</span><span class="se">\\</span><span class="s2">n&quot;</span><span class="p">,</span> <span class="n">argument</span> <span class="s2">&quot;format&quot;</span> <span class="k">for</span> <span class="nb">method</span> <span class="n">imp___stubs__printf</span>
<span class="n">mov</span>        <span class="n">dword</span> <span class="o">[</span><span class="n">rbp</span><span class="o">+</span><span class="n">var_4</span><span class="o">]</span><span class="p">,</span> <span class="mh">0x0</span>
<span class="n">mov</span>        <span class="n">al</span><span class="p">,</span> <span class="mh">0x0</span>
<span class="n">call</span>       <span class="n">imp___stubs__printf</span>
<span class="n">lea</span>        <span class="n">rdi</span><span class="p">,</span> <span class="n">qword</span> <span class="o">[</span><span class="mh">0x100000f9b</span><span class="o">]</span> <span class="p">;</span> <span class="s2">&quot;hello desgard</span><span class="se">\\</span><span class="s2">n&quot;</span><span class="p">,</span> <span class="n">argument</span> <span class="s2">&quot;format&quot;</span> <span class="k">for</span> <span class="nb">method</span> <span class="n">imp___stubs__printf</span>
<span class="n">mov</span>        <span class="n">dword</span> <span class="o">[</span><span class="n">rbp</span><span class="o">+</span><span class="n">var_8</span><span class="o">]</span><span class="p">,</span> <span class="n">eax</span>
<span class="n">mov</span>        <span class="n">al</span><span class="p">,</span> <span class="mh">0x0</span>
<span class="n">call</span>       <span class="n">imp___stubs__printf</span>
<span class="n">xor</span>        <span class="n">ecx</span><span class="p">,</span> <span class="n">ecx</span>
<span class="n">mov</span>        <span class="n">dword</span> <span class="o">[</span><span class="n">rbp</span><span class="o">+</span><span class="n">var_C</span><span class="o">]</span><span class="p">,</span> <span class="n">eax</span>
<span class="n">mov</span>        <span class="n">eax</span><span class="p">,</span> <span class="n">ecx</span>
<span class="n">add</span>        <span class="n">rsp</span><span class="p">,</span> <span class="mh">0x10</span>
<span class="n">pop</span>        <span class="n">rbp</span>
<span class="n">ret</span></code></pre></div>
<p>发现在使用 <code>printf</code> 的时候会触发 <code>call imp__stubs__printf</code> 这条指令。点击进入 <code>imp__stubs__printf</code> 的存储位置查看：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="ss">imp___stubs__printf</span><span class="p">:</span>
<span class="n">jmp</span>        <span class="n">qword</span> <span class="o">[</span><span class="n">_printf_ptr</span><span class="o">]</span> <span class="p">;</span> <span class="n">_printf</span><span class="p">,</span> <span class="no">CODE</span> <span class="no">XREF</span><span class="o">=</span><span class="n">_main</span><span class="o">+</span><span class="mi">24</span><span class="p">,</span> <span class="n">_main</span><span class="o">+</span><span class="mi">41</span>
<span class="p">;</span> <span class="o">================================</span>
<span class="p">;</span> <span class="err">继续跟踪</span><span class="o">.</span><span class="n">.</span><span class="o">.</span><span class="n">.</span>
<span class="mh">0x100001010</span>         <span class="n">dq</span>         <span class="n">_printf</span>  <span class="p">;</span> <span class="no">DATA</span> <span class="no">XREF</span><span class="o">=</span><span class="n">imp___stubs__printf</span></code></pre></div>
<p>在两个 <code>printf</code> 方法前加上断点，使用 <em>lldb</em> 对其进行调试：</p>
<p><img src="../assets/images/blog/15101394649922/15119414520860.jpg" alt="" /></p>
<p>为什么这里要观测 <code>0x10001010</code> 这个地址？因为 <code>imp___stubs__printf</code> 这个指针指向了它。这说明在 <code>__la_symbol_ptr</code> 表中对其进行了记录。使用 <em>MachOView</em> 对其进行验证：</p>
<p><img src="../assets/images/blog/15101394649922/15119418259666.jpg" alt="" /></p>
<p>发现在 <code>__DATA.__la_symbol_ptr</code> 中的记录值与 <em>lldb</em> 中 <code>x 0x10001010</code> 命令输出的记录值均为 <code>01 00 00 0f 84</code>。这个值对应的地址就是 <code>imp___stubs__printf</code> 这个桩位置。</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="mh">0x100000f84</span>         <span class="n">push</span>       <span class="mh">0x0</span>
<span class="mh">0x100000f89</span>         <span class="n">jmp</span>        <span class="mh">0x100000f74</span></code></pre></div>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="p">;</span> <span class="no">Section</span> <span class="n">__stub_helper</span>
<span class="mh">0x100000f74</span>         <span class="n">lea</span>        <span class="n">r11</span><span class="p">,</span> <span class="n">qword</span> <span class="o">[</span><span class="n">dyld_stub_binder_100001000</span><span class="o">+</span><span class="mi">8</span><span class="o">]</span>   <span class="p">;</span> <span class="no">CODE</span> <span class="no">XREF</span><span class="o">=</span><span class="mh">0x100000f89</span>
<span class="mh">0x100000f7b</span>         <span class="n">push</span>       <span class="n">r11</span>
<span class="mh">0x100000f7d</span>         <span class="n">jmp</span>        <span class="n">qword</span> <span class="o">[</span><span class="n">dyld_stub_binder_100001000</span><span class="o">]</span>          <span class="p">;</span> <span class="n">dyld_stub_binder</span>
<span class="mh">0x100000f83</span>         <span class="n">db</span>         <span class="mh">0x90</span>
<span class="mh">0x100000f84</span>         <span class="n">push</span>       <span class="mh">0x0</span>
<span class="mh">0x100000f89</span>         <span class="n">jmp</span>        <span class="mh">0x100000f74</span></code></pre></div>
<p>这个位置是不是似曾相识？是的，在<a href="http://www.desgard.com/iosre-1/">Mach-O 文件格式探索</a>一文中的验证试验，已经试验过了 Stub 机制。说道这里，我们在 MachOView 中的 <code>__TEXT.__stubs</code> 这个 Section 对其进行验证：</p>
<p><img src="../assets/images/blog/15101394649922/15119440533652.jpg" alt="" /></p>
<p>与预想中的结果是相同的。这个在 <code>__TEXT.__stub_helper</code> 中解析出的汇编代码和 <em>Hopper</em> 中完全一致。在这里通过 <code>_stub_helper</code> 来调用 <code>dyld_stu_binder</code> 方法计算 <code>printf</code> 函数的真实地址。其具体信息可以看出，<code>jmpq 0x100000f74</code> 就是在 <code>pushq</code> 参数 <code>$0x0</code> （link 过程中的标记值）后跳转到这个 section 的头部，并调用 <code>binder</code> 方法。</p>
<p><code>binder</code> 方法的作用简单来讲就是计算对应的函数地址进行绑定，之后进而调用对应函数。</p>
<p>在第二次输出 <code>0x10001010</code> 的值的时候，发现与第一次的值不相同了。变成了 <code>7f ff 76 cc 98 c4</code>。这个地址其实就是 <code>printf</code> 的真实地址。通过 <code>x</code> 命令及方法名的方式进行验证：</p>
<p><img src="../assets/images/blog/15101394649922/15119461061753.jpg" alt="" /></p>
<p>也就是说 <code>__DATA.__la_symbol_ptr</code> 中指向 <code>printf</code> 地址的值已经发生了改变，并真正的指向了 <code>printf</code> 指令。</p>
<h3 id="dynamic-symbol-table">Dynamic Symbol Table</h3>
<p><strong>Dynamic Symbol Table</strong> 动态符号表是用来加载动态库的时候导出的符号表，该表在 dyld 时期使用，并且在对象被加载的时候映射到进程的地址空间。所以我们可以说 DST 是符号表的子集。对于 ELF 来讲，DST 是有其定义的；但是对于 Mach-O 来说，我们可以理解为 DST 就是 <strong>Symbol Stubs</strong>。DST 和 Mach-O 中的 <em>Indirect Addressing</em> 有很大的关联，这里给出 Apple 对于 <em>Indirect Addressing</em> 的<a href="https://developer.apple.com/library/content/documentation/DeveloperTools/Conceptual/MachOTopics/1-Articles/indirect_addressing.html">官方文档</a>。</p>
<p>这个文档的主要内容是介绍了 <em>Non-lazy symbol references</em>、<em>Lazy symbol reference</em> 的特点和区别。其中 <em>Lazy symbol reference</em> 是指在方法在第一次调用的时候，会根据 dyld 过程时加载的映射地址进行处理，完成绑定操作。</p>
<p><img src="../assets/images/blog/15101394649922/15119572694685.jpg" alt="" /></p>
<p>【通过 MachOView 我们可以查看 <strong>Dynamic Symbol Table</strong> 中的所有 <em>Indirect Symbol</em>】</p>
<h2 id="fishhook">理解 fishhook</h2>
<h3 id="section-1">明确思路</h3>
<p>根据上面的实战内容，我们理解了 Lazy Binding 这个过程。受此启发，我们是不是可以重新绑定 Mach-O 的 Symbol 从而 hook 一些 C 中的库函数呢？这个思路在 <a href="https://github.com/facebook/fishhook">fishhook</a> 中已经实现。</p>
<h3 id="section-2">准备</h3>
<p>为了简化分析过程，我们引入了一个例子，之后的分析都会围绕着这个例子来进行。实例代码很简单，就是修改库函数 <code>strlen</code>，让其始终返回 <code>666</code>。</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1">#include &lt;stdio.h&gt;</span>
<span class="c1">#include &quot;fishhook.h&quot;</span>
<span class="n">static</span> <span class="n">int</span> <span class="p">(</span><span class="o">*</span><span class="n">original_strlen</span><span class="p">)(</span><span class="n">const</span> <span class="n">char</span> <span class="o">*</span><span class="n">_s</span><span class="p">);</span>
<span class="n">int</span> <span class="n">new_strlen</span><span class="p">(</span><span class="n">const</span> <span class="n">char</span> <span class="o">*</span><span class="n">_s</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="mi">666</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">int</span> <span class="n">main</span><span class="p">(</span><span class="n">int</span> <span class="n">argc</span><span class="p">,</span> <span class="n">const</span> <span class="n">char</span> <span class="o">*</span> <span class="n">argv</span><span class="o">[]</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">struct</span> <span class="n">rebinding</span> <span class="n">strlen_rebinding</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&quot;strlen&quot;</span><span class="p">,</span> <span class="n">new_strlen</span><span class="p">,</span>
        <span class="p">(</span><span class="n">void</span> <span class="o">*</span><span class="p">)</span><span class="o">&amp;</span><span class="n">original_strlen</span> <span class="p">};</span>
    <span class="n">rebind_symbols</span><span class="p">((</span><span class="n">struct</span> <span class="n">rebinding</span><span class="o">[</span><span class="mi">1</span><span class="o">]</span><span class="p">){</span> <span class="n">strlen_rebinding</span> <span class="p">},</span> <span class="mi">1</span><span class="p">);</span>
    <span class="n">char</span> <span class="o">*</span><span class="n">str</span> <span class="o">=</span> <span class="s2">&quot;hellolazy&quot;</span><span class="p">;</span>
    <span class="nb">printf</span><span class="p">(</span><span class="s2">&quot;%d</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">,</span> <span class="n">strlen</span><span class="p">(</span><span class="n">str</span><span class="p">));</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span></code></pre></div>
<p>在 <code>main</code> 方法中，我们构造了一个 <code>rebinding</code> 结构体实例，使用<strong>原方法名</strong>、<strong>新方法的指针</strong>以及<strong>一个二阶指针</strong>（后面我们可以了解到这里存储了 <code>__bss</code> 段中原方法的地址）。</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">struct</span> <span class="n">rebinding</span> <span class="p">{</span>
    <span class="n">const</span> <span class="n">char</span> <span class="o">*</span><span class="nb">name</span><span class="p">;</span>		<span class="sr">//</span> <span class="err">原方法名</span>
    <span class="n">void</span> <span class="o">*</span><span class="n">replacement</span><span class="p">;</span>		<span class="sr">//</span> <span class="err">新方法的代码段首地址</span>
    <span class="n">void</span> <span class="o">**</span><span class="n">replaced</span><span class="p">;</span>		<span class="sr">//</span> <span class="err">旧方法的指针</span>
<span class="p">};</span></code></pre></div>
<p>在跟踪代码之前，我先使用 <code>nm</code> 命令对生成的执行文件查看 File 中的符号信息（ <code>-n</code> 参数是根据已知地址进行排序）：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="err">$</span> <span class="n">nm</span> <span class="o">-</span><span class="n">n</span> <span class="no">TestObjcProj</span>
                 <span class="n">U</span> <span class="n">___memcpy_chk</span>
                 <span class="n">U</span> <span class="n">__dyld_get_image_header</span>
                 <span class="n">U</span> <span class="n">__dyld_get_image_vmaddr_slide</span>
                 <span class="n">U</span> <span class="n">__dyld_image_count</span>
                 <span class="n">U</span> <span class="n">__dyld_register_func_for_add_image</span>
                 <span class="n">U</span> <span class="n">_dladdr</span>
                 <span class="n">U</span> <span class="n">_free</span>
                 <span class="n">U</span> <span class="n">_malloc</span>
                 <span class="n">U</span> <span class="n">_printf</span>
                 <span class="n">U</span> <span class="n">_strcmp</span>
                 <span class="n">U</span> <span class="n">_strlen</span>
                 <span class="n">U</span> <span class="n">_strnlen</span>
                 <span class="n">U</span> <span class="n">dyld_stub_binder</span>
<span class="mo">0000000100000000</span> <span class="n">T</span> <span class="n">__mh_execute_header</span>
<span class="mo">00000001000006</span><span class="mi">90</span> <span class="n">t</span> <span class="n">_rebind_symbols_image</span>
<span class="mo">00000001000006</span><span class="n">f0</span> <span class="n">t</span> <span class="n">_prepend_rebindings</span>
<span class="mo">00000001000007</span><span class="n">d0</span> <span class="n">t</span> <span class="n">_rebind_symbols_for_image</span>
<span class="mo">0000000100000</span><span class="n">b00</span> <span class="n">t</span> <span class="n">_rebind_symbols</span>
<span class="mo">0000000100000</span><span class="n">bc0</span> <span class="n">t</span> <span class="n">__rebind_symbols_for_image</span>
<span class="mo">0000000100000</span><span class="n">bf0</span> <span class="n">t</span> <span class="n">_perform_rebinding_with_section</span>
<span class="mo">0000000100000</span><span class="n">e10</span> <span class="n">T</span> <span class="n">_new_strlen</span>
<span class="mo">0000000100000</span><span class="n">e20</span> <span class="n">T</span> <span class="n">_main</span>
<span class="mo">00000001000010</span><span class="mi">90</span> <span class="n">b</span> <span class="n">__rebindings_head</span>
<span class="mo">00000001000010</span><span class="mi">98</span> <span class="n">b</span> <span class="n">_original_strlen</span></code></pre></div>
<p>这里每一行代表一个符号，三列值分别代表<strong>地址</strong>，<strong>符号说明</strong> 和 <strong>符号名</strong>。其中符号说明为以下规则：</p>
<ul>
  <li>小写代表作用域为 <em>Local</em>，大写代表符号是 <em>Global(external)</em> 的。</li>
  <li><code>A</code> - 符号值是绝对的，在链接过程中不允许对其改变，这个符号常常出现在<strong>中断向量表</strong>中。</li>
  <li><code>B</code> - 符号值出现在内存 <em>BSS</em> 段。例如在某一个文件中定义全局的 <em>static</em> 方法 <code>static void test</code>，则符号 <em>test</em> 的类型为 <code>b</code>，切存储于 <em>BSS</em> 中。其值为该符号在 <em>BSS</em> 中的偏移。一般来说，<em>BSS</em> 分配在 RAM 中。</li>
  <li><code>C</code> - 称为<strong>Common Symbol</strong> <strong>一般符号</strong>，是为初始化的数据段。该符号不包含于普通的 Section 中。只有在链接过程才会进行分配。符号的值为所需要的字节数。</li>
  <li><code>D</code> - 称之为 <strong>Data Symbol</strong>。位于初始化数据段中。一般分配到 <em>Data Section</em> 中。例如全局的 <code>int table[5] = {233, 123, 321, 132, 231};</code>，会分配到初始化数据段中。</li>
  <li><code>T</code> - 该符号位于代码区 <em>TS</em> 中。</li>
  <li><code>U</code> - 说明<strong>当前文件中该符号是未定义的，该符号的定义在别的文件中</strong>。</li>
</ul>
<p>我们找到这几个与 <code>strlen</code> 先关的符号，先记录下来：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">U</span> <span class="n">_strlen</span>	<span class="c1"># 系统库方法，dyld 动态加载，所以未定义</span>
<span class="mo">0000000100000</span><span class="n">e10</span> <span class="n">T</span> <span class="n">_new_strlen</span>  <span class="c1"># 自定义方法，位于 Text Section</span>
<span class="mo">00000001000010</span><span class="mi">98</span> <span class="n">b</span> <span class="n">_original_strlen</span>  <span class="c1">#</span></code></pre></div>
<p>另外我们还需要了解的一些基地址信息。例如：<strong>Lazy Symbol Pointer Table</strong>(<code>__DATA.__la_symbol_ptr</code>)、<strong>Indirect Symbol Table</strong>、<strong>Symbol Table</strong>。这些均能从 <em>MachOView</em> 中获得到。</p>
<p><img src="../assets/images/blog/15101394649922/15133014200273.jpg" alt="" /></p>
<p>获取到 <code>__DATA.__la_symbol_ptr</code> 基地址为 <code>0x100001010</code>。</p>
<p><img src="../assets/images/blog/15101394649922/15133015712894.jpg" alt="" /></p>
<p>这里获取到在 <strong>Indirect Symbols</strong> 中，<code>_strlen</code> 符号的地址为 <code>0x1000025f0</code>。</p>
<p><img src="../assets/images/blog/15101394649922/15133019900468.jpg" alt="" /></p>
<p>这里可以获取到在 <strong>Symbol Table</strong> 中对应的符号位置 <code>0x100002560</code>，而且其中还能检索到在 <strong>String Table</strong> 中对应的符号 <code>_strlen</code> 的符号名称。</p>
<h3 id="section-3">调试代码</h3>
<p>在进行 hook 的代码逻辑中，在声明一个 <code>strlen_rebinding</code> 实例之后，需要手动调用方法 <code>rebind_symbols</code> ，其中需要传入一个 <code>rebinding</code> 数组的头地址，以及其 <em>size</em>。下面来看一下 <code>rebind_symbols</code> 方法的实现：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1">#define __SIZE_TYPE__ long unsigned int</span>
<span class="n">typedef</span> <span class="n">__SIZE_TYPE__</span> <span class="n">size_t</span><span class="p">;</span>
<span class="sr">/**</span>
<span class="sr"> * rebind_symbols</span>
<span class="sr"> * struct rebinding rebindings[] - rebinding 结构体数组</span>
<span class="sr"> * size_t rebindings_nel - 数组长度</span>
<span class="sr"> */</span>
<span class="n">int</span> <span class="n">rebind_symbols</span><span class="p">(</span><span class="n">struct</span> <span class="n">rebinding</span> <span class="n">rebindings</span><span class="o">[]</span><span class="p">,</span> <span class="n">size_t</span> <span class="n">rebindings_nel</span><span class="p">)</span> <span class="p">{</span>
    <span class="sr">//</span> <span class="err">维护一个</span> <span class="n">rebindings_entry</span> <span class="err">的结构</span>
    <span class="sr">//</span> <span class="err">将</span> <span class="n">rebinding</span> <span class="err">的多个实例组织成一个链表</span>
    <span class="n">int</span> <span class="n">retval</span> <span class="o">=</span> <span class="n">prepend_rebindings</span><span class="p">(</span><span class="o">&amp;</span><span class="n">_rebindings_head</span><span class="p">,</span> <span class="n">rebindings</span><span class="p">,</span> <span class="n">rebindings_nel</span><span class="p">);</span>
    <span class="sr">//</span> <span class="err">判断是否</span> <span class="n">malloc</span> <span class="err">失败，失败会返回</span> <span class="o">-</span><span class="mi">1</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">retval</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">retval</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="sr">//</span> <span class="n">_rebindings_head</span> <span class="o">-&gt;</span> <span class="k">next</span> <span class="err">是第一次调用的标志符，</span><span class="no">NULL</span> <span class="err">则代表第一次调用</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">_rebindings_head</span><span class="o">-&gt;</span><span class="k">next</span><span class="p">)</span> <span class="p">{</span>
        <span class="sr">//</span> <span class="err">第一次调用，将</span> <span class="n">_rebind_symbols_for_image</span> <span class="err">注册为回调</span>
        <span class="n">_dyld_register_func_for_add_image</span><span class="p">(</span><span class="n">_rebind_symbols_for_image</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="sr">//</span> <span class="err">先获取</span> <span class="n">dyld</span> <span class="err">镜像数量</span>
        <span class="n">uint32_t</span> <span class="n">c</span> <span class="o">=</span> <span class="n">_dyld_image_count</span><span class="p">();</span>
        <span class="k">for</span> <span class="p">(</span><span class="n">uint32_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">c</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
            <span class="sr">//</span> <span class="err">根据下标依次进行重绑定过程</span>
            <span class="n">_rebind_symbols_for_image</span><span class="p">(</span><span class="n">_dyld_get_image_header</span><span class="p">(</span><span class="n">i</span><span class="p">),</span> <span class="n">_dyld_get_image_vmaddr_slide</span><span class="p">(</span><span class="n">i</span><span class="p">));</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="sr">//</span> <span class="err">返回状态值</span>
    <span class="k">return</span> <span class="n">retval</span><span class="p">;</span>
<span class="p">}</span></code></pre></div>
<p>为了<strong>将多次绑定时的多个符号</strong>组织成一个链式结构，<em>fishhook</em> 自定义了一个链表结构来组织这个逻辑，其中每个节点的数据结构如下：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">struct</span> <span class="n">rebindings_entry</span> <span class="p">{</span>
    <span class="n">struct</span> <span class="n">rebinding</span> <span class="o">*</span><span class="n">rebindings</span><span class="p">;</span> <span class="sr">//</span> <span class="n">rebinding</span> <span class="err">数组实例</span>
    <span class="n">size_t</span> <span class="n">rebindings_nel</span><span class="p">;</span> <span class="sr">//</span> <span class="err">元素数量</span>
    <span class="n">struct</span> <span class="n">rebindings_entry</span> <span class="o">*</span><span class="k">next</span><span class="p">;</span> <span class="sr">//</span> <span class="err">链表索引</span>
<span class="p">};</span>
<span class="sr">//</span> <span class="err">全局量，直接拿出表头</span>
<span class="n">static</span> <span class="n">struct</span> <span class="n">rebindings_entry</span> <span class="o">*</span><span class="n">_rebindings_head</span><span class="p">;</span></code></pre></div>
<p>在 <code>prepend_rebindings</code> 方法中，<em>fishhook</em> 会维护这个结构：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="sr">/**</span>
<span class="sr"> * prepend_rebindings 用于 rebindings_entry 结构的维护</span>
<span class="sr"> * struct rebindings_entry **rebindings_head - 对应的是 static 的 _rebindings_head</span>
<span class="sr"> * struct rebinding rebindings[] - 传入的方法符号数组</span>
<span class="sr"> * size_t nel - 数组对应的元素数量</span>
<span class="sr"> */</span>
<span class="n">static</span> <span class="n">int</span> <span class="n">prepend_rebindings</span><span class="p">(</span><span class="n">struct</span> <span class="n">rebindings_entry</span> <span class="o">**</span><span class="n">rebindings_head</span><span class="p">,</span>
                              <span class="n">struct</span> <span class="n">rebinding</span> <span class="n">rebindings</span><span class="o">[]</span><span class="p">,</span>
                              <span class="n">size_t</span> <span class="n">nel</span><span class="p">)</span> <span class="p">{</span>
    <span class="sr">//</span> <span class="err">声明</span> <span class="n">rebindings_entry</span> <span class="err">一个指针，并为其分配空间</span>
    <span class="n">struct</span> <span class="n">rebindings_entry</span> <span class="o">*</span><span class="n">new_entry</span> <span class="o">=</span> <span class="p">(</span><span class="n">struct</span> <span class="n">rebindings_entry</span> <span class="o">*</span><span class="p">)</span> <span class="n">malloc</span><span class="p">(</span><span class="n">sizeof</span><span class="p">(</span><span class="n">struct</span> <span class="n">rebindings_entry</span><span class="p">));</span>
    <span class="sr">//</span> <span class="err">分配空间失败的容错处理</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">new_entry</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="sr">//</span> <span class="err">为链表中元素的</span> <span class="n">rebindings</span> <span class="err">实例分配指定空间</span>
    <span class="n">new_entry</span><span class="o">-&gt;</span><span class="n">rebindings</span> <span class="o">=</span> <span class="p">(</span><span class="n">struct</span> <span class="n">rebinding</span> <span class="o">*</span><span class="p">)</span> <span class="n">malloc</span><span class="p">(</span><span class="n">sizeof</span><span class="p">(</span><span class="n">struct</span> <span class="n">rebinding</span><span class="p">)</span> <span class="o">*</span> <span class="n">nel</span><span class="p">);</span>
    <span class="sr">//</span> <span class="err">分配空间失败的容错处理</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">new_entry</span><span class="o">-&gt;</span><span class="n">rebindings</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">free</span><span class="p">(</span><span class="n">new_entry</span><span class="p">);</span>
        <span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="sr">//</span> <span class="err">将</span> <span class="n">rebindings</span> <span class="err">数组中</span> <span class="n">copy</span> <span class="err">到</span> <span class="n">new_entry</span> <span class="o">-&gt;</span> <span class="n">rebingdings</span> <span class="err">成员中</span>
    <span class="n">memcpy</span><span class="p">(</span><span class="n">new_entry</span><span class="o">-&gt;</span><span class="n">rebindings</span><span class="p">,</span> <span class="n">rebindings</span><span class="p">,</span> <span class="n">sizeof</span><span class="p">(</span><span class="n">struct</span> <span class="n">rebinding</span><span class="p">)</span> <span class="o">*</span> <span class="n">nel</span><span class="p">);</span>
    <span class="sr">//</span> <span class="err">为</span> <span class="n">new_entry</span> <span class="o">-&gt;</span> <span class="n">rebindings_nel</span> <span class="err">赋值</span>
    <span class="n">new_entry</span><span class="o">-&gt;</span><span class="n">rebindings_nel</span> <span class="o">=</span> <span class="n">nel</span><span class="p">;</span>
    <span class="sr">//</span> <span class="err">为</span> <span class="n">new_entry</span> <span class="o">-&gt;</span> <span class="n">newx</span> <span class="err">赋值，维护链表结构</span>
    <span class="n">new_entry</span><span class="o">-&gt;</span><span class="k">next</span> <span class="o">=</span> <span class="o">*</span><span class="n">rebindings_head</span><span class="p">;</span>
    <span class="sr">//</span> <span class="err">移动</span> <span class="n">head</span> <span class="err">指针，指向表头</span>
    <span class="o">*</span><span class="n">rebindings_head</span> <span class="o">=</span> <span class="n">new_entry</span><span class="p">;</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span></code></pre></div>
<p>之后这一部分的结构将会持续被维护，用图示来说明一下：</p>
<p><img src="../assets/images/blog/15101394649922/15133214554985.jpg" alt="" /></p>
<p>在执行完链表的初始化及结构维护的 <code>prepend_rebindings</code> 方法，继续执行。由于我们的 <code>strlen</code> 是 dyld 加载的系统库方法，所以 <code>_rebindings_head -&gt; next</code> 在第一次调用的时候为空，因为没有做过替换符号，所以会调用 <code>_dyld_register_func_for_add_image</code> 来注册 <code>_rebind_symbols_for_image</code> 方法，之后程序每次加载动态库的时候，都会去调用该方法。如果不是第一次替换符号，则<strong>遍历已经加载的动态库</strong>。</p>
<p>这一块的流程也是 <em>fishhook</em> 代码的精髓部分，逐一来分析这些地方。</p>
<h4 id="dyldregisterfuncforaddimage-"><code>_dyld_register_func_for_add_image</code> 是什么？</h4>
<p>这里笔者查看了 Apple 官方的 <code>dyld.h</code> 头文件，其中对于 <code>_dyld_register_func_for_add_image</code> 有较为详细的说明（<a href="https://opensource.apple.com/source/dyld/dyld-433.5/include/mach-o/dyld.h.auto.html">dyld.h</a>）：</p>
<blockquote>
  <p>The following functions allow you to install callbacks which will be called by dyld whenever an image is loaded or unloaded.  During a call to _dyld_register_func_for_add_image() the callback func is called for every existing image.  Later, it is called as each new image is loaded and bound (but initializers not yet run).  The callback registered with _dyld_register_func_for_remove_image() is called after any terminators in an image are run and before the image is un-memory-mapped.</p>
</blockquote>
<p><code>_dyld_register_func_for_add_image</code> 这个方法当镜像 <em>Image</em> 被 <em>load</em> 或是 <em>unload</em> 的时候都会由 dyld 主动调用。当该方法被触发时，会为每个镜像触发其回调方法。之后则将其镜像与其回电函数进行绑定（但是未进行初始化）。使用 <code>_dyld_register_func_for_add_image</code> 注册的回调将在镜像中的 terminators 启动后被调用。</p>
<h4 id="rebindsymbolsforimage-">为什么传入的 <code>_rebind_symbols_for_image</code> 作为回调函数呢？</h4>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">extern</span> <span class="n">void</span> <span class="n">_dyld_register_func_for_add_image</span><span class="p">(</span><span class="n">void</span> <span class="p">(</span><span class="o">*</span><span class="n">func</span><span class="p">)(</span><span class="n">const</span> <span class="n">struct</span> <span class="n">mach_header</span><span class="o">*</span> <span class="n">mh</span><span class="p">,</span> <span class="n">intptr_t</span> <span class="n">vmaddr_slide</span><span class="p">))</span>    <span class="n">__OSX_AVAILABLE_STARTING</span><span class="p">(</span><span class="n">__MAC_10_1</span><span class="p">,</span> <span class="n">__IPHONE_2_0</span><span class="p">);</span>
<span class="n">extern</span> <span class="n">void</span> <span class="n">_dyld_register_func_for_remove_image</span><span class="p">(</span><span class="n">void</span> <span class="p">(</span><span class="o">*</span><span class="n">func</span><span class="p">)(</span><span class="n">const</span> <span class="n">struct</span> <span class="n">mach_header</span><span class="o">*</span> <span class="n">mh</span><span class="p">,</span> <span class="n">intptr_t</span> <span class="n">vmaddr_slide</span><span class="p">))</span> <span class="n">__OSX_AVAILABLE_STARTING</span><span class="p">(</span><span class="n">__MAC_10_1</span><span class="p">,</span> <span class="n">__IPHONE_2_0</span><span class="p">);</span></code></pre></div>
<p><code>extern</code> 关键字来告知编译器<strong>当调用方法的时候，请在其他模块中寻找该方法定义</strong>。并且在这两个方法的声明中，所注册方法的参数列表一定是 <code>(const struct mach_header* mh, intptr_t vmaddr_slide)</code>。</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="sr">/**</span>
<span class="sr"> * _rebind_symbols_for_image 是 rebind_symbols_for_image 的一个入口方法</span>
<span class="sr"> * 这个入口方法存在的意义是满足 _dyld_register_func_for_add_image 传入回调方法的格式</span>
<span class="sr"> * header - Mach-O 头</span>
<span class="sr"> * slide - intptr_t 持有指针</span>
<span class="sr"> */</span>
<span class="n">static</span> <span class="n">void</span> <span class="n">_rebind_symbols_for_image</span><span class="p">(</span><span class="n">const</span> <span class="n">struct</span> <span class="n">mach_header</span> <span class="o">*</span><span class="n">header</span><span class="p">,</span>
                                      <span class="n">intptr_t</span> <span class="n">slide</span><span class="p">)</span> <span class="p">{</span>
    <span class="sr">//</span> <span class="err">外层是一个入口函数，意在调用有效的方法</span> <span class="n">rebind_symbols_for_image</span>
    <span class="n">rebind_symbols_for_image</span><span class="p">(</span><span class="n">_rebindings_head</span><span class="p">,</span> <span class="n">header</span><span class="p">,</span> <span class="n">slide</span><span class="p">);</span>
<span class="p">}</span></code></pre></div>
<p>看到 <code>_rebind_symbols_for_image</code> 是个入口，我们的问题也就迎刃而解了。添加这个入口方法其实是为了适应回调函数的参数格式，但是真正调用的 <code>rebind_symbols_for_image</code> 所需要的参数不满足其方法的描述。</p>
<h4 id="intptrt-"><code>intptr_t</code> 是什么类型？</h4>
<p>至于 <code>intptr_t</code>，笔者在之前阅读 <a href="https://book.douban.com/subject/25827246/"><em>Understanding and using C pointers</em></a> 时见过。下面为引用：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="sr">//</span><span class="o">/</span> <span class="sr">/usr/in</span><span class="n">clude</span><span class="o">/</span><span class="n">stdint</span><span class="o">.</span><span class="n">h</span>
<span class="sr">/* Types for `void *&#39; pointers. */</span>
<span class="c1">#if __WORDSIZE == 64</span>
<span class="c1"># ifndef __intptr_t_defined</span>
<span class="n">typedef</span> <span class="n">long</span> <span class="n">int</span> <span class="n">intptr_t</span><span class="p">;</span>
<span class="c1"># define __intptr_t_defined</span>
<span class="c1"># endif</span>
<span class="n">typedef</span> <span class="n">unsigned</span> <span class="n">long</span> <span class="n">int</span> <span class="n">uintptr_t</span><span class="p">;</span>
<span class="c1">#else</span>
<span class="c1"># ifndef __intptr_t_defined</span>
<span class="n">typedef</span> <span class="n">int</span> <span class="n">intptr_t</span><span class="p">;</span>
<span class="c1"># define __intptr_t_defined</span>
<span class="c1"># endif</span>
<span class="n">typedef</span> <span class="n">unsigned</span> <span class="n">int</span> <span class="n">uintptr_t</span><span class="p">;</span>
<span class="c1">#endif</span></code></pre></div>
<blockquote>
  <p><code>intptr_t</code> 和 <code>uintptr_t</code> 这两种类型用于存储指针地址。从字面上看，<code>intptr_t</code> 像是一个整型指针类型，而且在 <code>stdint.h</code> 的注释中也可以看到 <em>Types for void *pointers</em> 的描述，但我们看到实际上 <code>intptr_t</code> 就是一个整数，在 64 位平台中定义为 <code>long int</code>，否则定义为 <code>int</code>。我们知道 C 语言的指针实际上就是变量的地址，在 64 位平台上指针为 8 字节，在 32 位平台上指针为 4 字节，而 <code>intptr_t</code> 刚好是和这个字节数对应，使用它可以安全地进行整数与指针的转换运算，也就是说当需要将指针作为整数运算时，将它转换成 <code>intptr_t</code> 进行运算才是安全的。<strong>使用 <code>int</code> 时也可以使用 <code>intptr_t</code> 来保证平台的通用性，它在不同的平台上编译时长度不同，但都是标准的平台字长</strong>。</p>
</blockquote>
<p>简而言之，这是一个标准字长的存储量，用来代表方法地址的偏移量。</p>
<h3 id="section-4">重绑定寻址 - 基址准备过程</h3>
<p><code>rebind_symbols_for_image</code> 方法描述的也就是整个 <em>fishhook</em> 精华所在 - <strong>重绑定符号过程</strong>。继续跟踪代码来理解每一行的意图：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">static</span> <span class="n">void</span> <span class="n">rebind_symbols_for_image</span><span class="p">(</span><span class="n">struct</span> <span class="n">rebindings_entry</span> <span class="o">*</span><span class="n">rebindings</span><span class="p">,</span>
                                     <span class="n">const</span> <span class="n">struct</span> <span class="n">mach_header</span> <span class="o">*</span><span class="n">header</span><span class="p">,</span>
                                     <span class="n">intptr_t</span> <span class="n">slide</span><span class="p">)</span> <span class="p">{</span>
    <span class="no">Dl_info</span> <span class="n">info</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">dladdr</span><span class="p">(</span><span class="n">header</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">info</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="sr">//</span> <span class="err">声明几个查找量</span><span class="p">:</span>
    <span class="sr">//</span> <span class="n">linkedit_segment</span><span class="p">,</span> <span class="n">symtab_command</span><span class="p">,</span> <span class="n">dysymtab_command</span>
    <span class="n">segment_command_t</span> <span class="o">*</span><span class="n">cur_seg_cmd</span><span class="p">;</span>
    <span class="n">segment_command_t</span> <span class="o">*</span><span class="n">linkedit_segment</span> <span class="o">=</span> <span class="no">NULL</span><span class="p">;</span>
    <span class="n">struct</span> <span class="n">symtab_command</span><span class="o">*</span> <span class="n">symtab_cmd</span> <span class="o">=</span> <span class="no">NULL</span><span class="p">;</span>
    <span class="n">struct</span> <span class="n">dysymtab_command</span><span class="o">*</span> <span class="n">dysymtab_cmd</span> <span class="o">=</span> <span class="no">NULL</span><span class="p">;</span>
    <span class="sr">//</span> <span class="err">初始化游标</span>
    <span class="sr">//</span> <span class="n">header</span> <span class="o">=</span> <span class="mh">0x100000000</span> <span class="o">-</span> <span class="err">二进制文件基址默认偏移</span>
    <span class="sr">//</span> <span class="n">sizeof</span><span class="p">(</span><span class="n">mach_header_t</span><span class="p">)</span> <span class="o">=</span> <span class="mh">0x20</span> <span class="o">-</span> <span class="no">Mach</span><span class="o">-</span><span class="n">O</span> <span class="no">Header</span> <span class="err">部分</span>
    <span class="sr">//</span> <span class="err">首先需要跳过</span> <span class="no">Mach</span><span class="o">-</span><span class="n">O</span> <span class="no">Header</span>
    <span class="n">uintptr_t</span> <span class="n">cur</span> <span class="o">=</span> <span class="p">(</span><span class="n">uintptr_t</span><span class="p">)</span><span class="n">header</span> <span class="o">+</span> <span class="n">sizeof</span><span class="p">(</span><span class="n">mach_header_t</span><span class="p">);</span>
    <span class="sr">//</span> <span class="err">遍历每一个</span> <span class="no">Load</span> <span class="no">Command</span><span class="err">，游标每一次偏移每个命令的</span> <span class="no">Command</span> <span class="no">Size</span> <span class="err">大小</span>
    <span class="sr">//</span> <span class="n">header</span> <span class="o">-&gt;</span> <span class="ss">ncmds</span><span class="p">:</span> <span class="no">Load</span> <span class="no">Command</span> <span class="err">加载命令数量</span>
    <span class="sr">//</span> <span class="n">cur_seg_cmd</span> <span class="o">-&gt;</span> <span class="ss">cmdsize</span><span class="p">:</span> <span class="no">Load</span> <span class="err">大小</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">uint</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">header</span><span class="o">-&gt;</span><span class="n">ncmds</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">,</span> <span class="n">cur</span> <span class="o">+=</span> <span class="n">cur_seg_cmd</span><span class="o">-&gt;</span><span class="n">cmdsize</span><span class="p">)</span> <span class="p">{</span>
        <span class="sr">//</span> <span class="err">取出当前的</span> <span class="no">Load</span> <span class="no">Command</span>
        <span class="n">cur_seg_cmd</span> <span class="o">=</span> <span class="p">(</span><span class="n">segment_command_t</span> <span class="o">*</span><span class="p">)</span><span class="n">cur</span><span class="p">;</span>
        <span class="sr">//</span> <span class="no">Load</span> <span class="no">Command</span> <span class="err">的类型是</span> <span class="no">LC_SEGMENT</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">cur_seg_cmd</span><span class="o">-&gt;</span><span class="n">cmd</span> <span class="o">==</span> <span class="no">LC_SEGMENT_ARCH_DEPENDENT</span><span class="p">)</span> <span class="p">{</span>
            <span class="sr">//</span> <span class="err">比对一下</span> <span class="no">Load</span> <span class="no">Command</span> <span class="err">的</span> <span class="nb">name</span> <span class="err">是否为</span> <span class="n">__LINKEDIT</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="n">cur_seg_cmd</span><span class="o">-&gt;</span><span class="n">segname</span><span class="p">,</span> <span class="no">SEG_LINKEDIT</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
                <span class="sr">//</span> <span class="err">检索到</span> <span class="n">__LINKEDIT</span>
                <span class="n">linkedit_segment</span> <span class="o">=</span> <span class="n">cur_seg_cmd</span><span class="p">;</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="sr">//</span> <span class="err">判断当前</span> <span class="no">Load</span> <span class="no">Command</span> <span class="err">是否是</span> <span class="no">LC_SYMTAB</span> <span class="err">类型</span>
        <span class="sr">//</span> <span class="no">LC_SEGMENT</span> <span class="o">-</span> <span class="err">代表当前区域链接器信息</span>
        <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">cur_seg_cmd</span><span class="o">-&gt;</span><span class="n">cmd</span> <span class="o">==</span> <span class="no">LC_SYMTAB</span><span class="p">)</span> <span class="p">{</span>
            <span class="sr">//</span> <span class="err">检索到</span> <span class="no">LC_SYMTAB</span>
            <span class="n">symtab_cmd</span> <span class="o">=</span> <span class="p">(</span><span class="n">struct</span> <span class="n">symtab_command</span><span class="o">*</span><span class="p">)</span><span class="n">cur_seg_cmd</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="sr">//</span> <span class="err">判断当前</span> <span class="no">Load</span> <span class="no">Command</span> <span class="err">是否是</span> <span class="no">LC_DYSYMTAB</span> <span class="err">类型</span>
        <span class="sr">//</span> <span class="no">LC_DYSYMTAB</span> <span class="o">-</span> <span class="err">代表动态链接器信息区域</span>
        <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">cur_seg_cmd</span><span class="o">-&gt;</span><span class="n">cmd</span> <span class="o">==</span> <span class="no">LC_DYSYMTAB</span><span class="p">)</span> <span class="p">{</span>
            <span class="sr">//</span> <span class="err">检索到</span> <span class="no">LC_DYSYMTAB</span>
            <span class="n">dysymtab_cmd</span> <span class="o">=</span> <span class="p">(</span><span class="n">struct</span> <span class="n">dysymtab_command</span><span class="o">*</span><span class="p">)</span><span class="n">cur_seg_cmd</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="sr">//</span> <span class="err">容错处理</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">symtab_cmd</span> <span class="o">||</span> <span class="o">!</span><span class="n">dysymtab_cmd</span> <span class="o">||</span> <span class="o">!</span><span class="n">linkedit_segment</span> <span class="o">||</span>
        <span class="o">!</span><span class="n">dysymtab_cmd</span><span class="o">-&gt;</span><span class="n">nindirectsyms</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="sr">//</span> <span class="ss">slide</span><span class="p">:</span> <span class="no">ASLR</span> <span class="err">偏移量</span>
    <span class="sr">//</span> <span class="ss">vmaddr</span><span class="p">:</span> <span class="no">SEG_LINKEDIT</span> <span class="err">的虚拟地址</span>
    <span class="sr">//</span> <span class="ss">fileoff</span><span class="p">:</span> <span class="no">SEG_LINKEDIT</span> <span class="err">地址偏移</span>
    <span class="sr">//</span> <span class="err">式①：</span><span class="n">base</span> <span class="o">=</span> <span class="no">SEG_LINKEDIT</span><span class="err">真实地址</span> <span class="o">-</span> <span class="no">SEG_LINKEDIT</span><span class="err">地址偏移</span>
    <span class="sr">//</span> <span class="err">式②：</span><span class="no">SEG_LINKEDIT</span><span class="err">真实地址</span> <span class="o">=</span> <span class="no">SEG_LINKEDIT</span><span class="err">虚拟地址</span> <span class="o">+</span> <span class="no">ASLR</span><span class="err">偏移量</span>
    <span class="sr">//</span> <span class="err">将②代入①：</span><span class="no">Base</span> <span class="o">=</span> <span class="no">SEG_LINKEDIT</span><span class="err">虚拟地址</span> <span class="o">+</span> <span class="no">ASLR</span><span class="err">偏移量</span> <span class="o">-</span> <span class="no">SEG_LINKEDIT</span><span class="err">地址偏移</span>
    <span class="n">uintptr_t</span> <span class="n">linkedit_base</span> <span class="o">=</span> <span class="p">(</span><span class="n">uintptr_t</span><span class="p">)</span><span class="n">slide</span> <span class="o">+</span> <span class="n">linkedit_segment</span><span class="o">-&gt;</span><span class="n">vmaddr</span> <span class="o">-</span> <span class="n">linkedit_segment</span><span class="o">-&gt;</span><span class="n">fileoff</span><span class="p">;</span>
    <span class="sr">//</span> <span class="err">通过</span> <span class="n">base</span> <span class="o">+</span> <span class="n">symtab</span> <span class="err">的偏移量</span> <span class="err">计算</span> <span class="n">symtab</span> <span class="err">表的首地址，并获取</span> <span class="n">nlist_t</span> <span class="err">结构体实例</span>
    <span class="n">nlist_t</span> <span class="o">*</span><span class="n">symtab</span> <span class="o">=</span> <span class="p">(</span><span class="n">nlist_t</span> <span class="o">*</span><span class="p">)(</span><span class="n">linkedit_base</span> <span class="o">+</span> <span class="n">symtab_cmd</span><span class="o">-&gt;</span><span class="n">symoff</span><span class="p">);</span>
    <span class="sr">//</span> <span class="err">通过</span> <span class="n">base</span> <span class="o">+</span> <span class="n">stroff</span> <span class="err">字符表偏移量计算字符表中的首地址，获取字符串表</span>
    <span class="n">char</span> <span class="o">*</span><span class="n">strtab</span> <span class="o">=</span> <span class="p">(</span><span class="n">char</span> <span class="o">*</span><span class="p">)(</span><span class="n">linkedit_base</span> <span class="o">+</span> <span class="n">symtab_cmd</span><span class="o">-&gt;</span><span class="n">stroff</span><span class="p">);</span>
    <span class="sr">//</span> <span class="err">通过</span> <span class="n">base</span> <span class="o">+</span> <span class="n">indirectsymoff</span> <span class="err">偏移量来计算动态符号表的首地址</span>
    <span class="n">uint32_t</span> <span class="o">*</span><span class="n">indirect_symtab</span> <span class="o">=</span> <span class="p">(</span><span class="n">uint32_t</span> <span class="o">*</span><span class="p">)(</span><span class="n">linkedit_base</span> <span class="o">+</span> <span class="n">dysymtab_cmd</span><span class="o">-&gt;</span><span class="n">indirectsymoff</span><span class="p">);</span>
    <span class="sr">//</span> <span class="err">归零游标，复用</span>
    <span class="n">cur</span> <span class="o">=</span> <span class="p">(</span><span class="n">uintptr_t</span><span class="p">)</span><span class="n">header</span> <span class="o">+</span> <span class="n">sizeof</span><span class="p">(</span><span class="n">mach_header_t</span><span class="p">);</span>
    <span class="sr">//</span> <span class="err">再次遍历</span> <span class="no">Load</span> <span class="no">Commands</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">uint</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">header</span><span class="o">-&gt;</span><span class="n">ncmds</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">,</span> <span class="n">cur</span> <span class="o">+=</span> <span class="n">cur_seg_cmd</span><span class="o">-&gt;</span><span class="n">cmdsize</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">cur_seg_cmd</span> <span class="o">=</span> <span class="p">(</span><span class="n">segment_command_t</span> <span class="o">*</span><span class="p">)</span><span class="n">cur</span><span class="p">;</span>
        <span class="sr">//</span> <span class="no">Load</span> <span class="no">Command</span> <span class="err">的类型是</span> <span class="no">LC_SEGMENT</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">cur_seg_cmd</span><span class="o">-&gt;</span><span class="n">cmd</span> <span class="o">==</span> <span class="no">LC_SEGMENT_ARCH_DEPENDENT</span><span class="p">)</span> <span class="p">{</span>
            <span class="sr">//</span> <span class="err">查询</span> <span class="no">Segment</span> <span class="no">Name</span> <span class="err">过滤出</span> <span class="n">__DATA</span> <span class="err">或者</span> <span class="n">__DATA_CONST</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="n">cur_seg_cmd</span><span class="o">-&gt;</span><span class="n">segname</span><span class="p">,</span> <span class="no">SEG_DATA</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span>
                <span class="n">strcmp</span><span class="p">(</span><span class="n">cur_seg_cmd</span><span class="o">-&gt;</span><span class="n">segname</span><span class="p">,</span> <span class="no">SEG_DATA_CONST</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">continue</span><span class="p">;</span>
            <span class="p">}</span>
            <span class="sr">//</span> <span class="err">遍历</span> <span class="no">Segment</span> <span class="err">中的</span> <span class="no">Section</span>
            <span class="k">for</span> <span class="p">(</span><span class="n">uint</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">j</span> <span class="o">&lt;</span> <span class="n">cur_seg_cmd</span><span class="o">-&gt;</span><span class="n">nsects</span><span class="p">;</span> <span class="n">j</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
                <span class="sr">//</span> <span class="err">取出</span> <span class="no">Section</span>
                <span class="n">section_t</span> <span class="o">*</span><span class="n">sect</span> <span class="o">=</span> <span class="p">(</span><span class="n">section_t</span> <span class="o">*</span><span class="p">)(</span><span class="n">cur</span> <span class="o">+</span> <span class="n">sizeof</span><span class="p">(</span><span class="n">segment_command_t</span><span class="p">))</span> <span class="o">+</span> <span class="n">j</span><span class="p">;</span>
                <span class="sr">//</span> <span class="n">flags</span> <span class="o">&amp;</span> <span class="no">SECTION_TYPE</span> <span class="err">通过</span> <span class="no">SECTION_TYPE</span> <span class="err">掩码获取</span> <span class="n">flags</span> <span class="err">记录类型的</span> <span class="mi">8</span> <span class="n">bit</span>
                <span class="sr">//</span> <span class="err">如果</span> <span class="n">section</span> <span class="err">的类型为</span> <span class="no">S_LAZY_SYMBOL_POINTERS</span>
                <span class="sr">//</span> <span class="err">这个类型代表</span> <span class="n">lazy</span> <span class="n">symbol</span> <span class="err">指针</span> <span class="no">Section</span>
                <span class="k">if</span> <span class="p">((</span><span class="n">sect</span><span class="o">-&gt;</span><span class="n">flags</span> <span class="o">&amp;</span> <span class="no">SECTION_TYPE</span><span class="p">)</span> <span class="o">==</span> <span class="no">S_LAZY_SYMBOL_POINTERS</span><span class="p">)</span> <span class="p">{</span>
                    <span class="sr">//</span> <span class="err">进行</span> <span class="n">rebinding</span> <span class="err">重写操作</span>
                    <span class="n">perform_rebinding_with_section</span><span class="p">(</span><span class="n">rebindings</span><span class="p">,</span> <span class="n">sect</span><span class="p">,</span> <span class="n">slide</span><span class="p">,</span> <span class="n">symtab</span><span class="p">,</span> <span class="n">strtab</span><span class="p">,</span> <span class="n">indirect_symtab</span><span class="p">);</span>
                <span class="p">}</span>
                <span class="sr">//</span> <span class="err">这个类型代表</span> <span class="n">non</span><span class="o">-</span><span class="n">lazy</span> <span class="n">symbol</span> <span class="err">指针</span> <span class="no">Section</span>
                <span class="k">if</span> <span class="p">((</span><span class="n">sect</span><span class="o">-&gt;</span><span class="n">flags</span> <span class="o">&amp;</span> <span class="no">SECTION_TYPE</span><span class="p">)</span> <span class="o">==</span> <span class="no">S_NON_LAZY_SYMBOL_POINTERS</span><span class="p">)</span> <span class="p">{</span>
                    <span class="n">perform_rebinding_with_section</span><span class="p">(</span><span class="n">rebindings</span><span class="p">,</span> <span class="n">sect</span><span class="p">,</span> <span class="n">slide</span><span class="p">,</span> <span class="n">symtab</span><span class="p">,</span> <span class="n">strtab</span><span class="p">,</span> <span class="n">indirect_symtab</span><span class="p">);</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></div>
<p><code>rebind_symbols_for_image</code> 方法展示了冲绑定过程中，所有计算地址的流程。浏览过代码之后思考一个问题：<strong>为了完成重绑定的操作，我们需要获取哪些地址信息呢？</strong></p>
<h4 id="linkedit-base-addr">1. 获取 Linkedit Base Addr</h4>
<p><em>fishhook</em> 绝大多数重要的地址计算都要使用到或是间接使用到 <strong>Linkedit Base Addr</strong>。要如何理解这个基地址呢？这里我们可以理解成： <strong>Linkedit Segment 在文件中的首地址</strong>。</p>
<p>为什么 <em>Linkedit Segment</em> 首地址信息十分重要，因为在 <em>Load Command</em> 中，<code>LC_SYMTAB</code> 和 <code>LC_DYSYMTAB</code> 的中所记录的 Offset 都是基于 <strong>__LINKEDIT</strong> 段的。而 <code>LC_SYMTAB</code> 中通过偏移量可以拿到<strong>symtab 符号表首地址</strong>、<strong>strtab 符号名称字符表首地址</strong>以及<strong>indirect_symtab 跳转表首地址</strong>。</p>
<p>我们拿到 <strong>Indirect Symbols</strong> 的首地址 <code>indirect_symtab</code> 再加上 <code>LC_SEGMENT.__DATA</code> 中任何一个 <em>Section</em> 信息的 <code>reverved1</code> 字段就可以获取到对应的 <em>Indirect Address</em> 信息。在这之后我们可以遍历每一个 <strong>Indirect Symbols</strong>，并以索引方式获取到每一个 <code>nlist</code> 结构的符号，从符号中获取到符号名字符串在字符表中的偏移量，进而继续获取符号名。</p>
<p><img src="../assets/images/blog/15101394649922/15134938187077.jpg" alt="" /></p>
<h4 id="load-command-">2. 二次遍历 Load Command 的目的</h4>
<p>第一次遍历的目的在第一个疑问中已经解释的很清楚了，遍历目的有三个，为了找出 <code>LC_SEGMENT(__LINKEDIT)</code>、<code>LC_SYMTAB</code> 和 <code>LC_DYSYMTAB</code> 三个 <strong>Load Command</strong>，从而我们可以计算出 <em>Base Address</em>、<em>Symbol Table</em>、<em>Dynamic Symbol</em> 和 <em>String Table</em> 的位置。</p>
<p>而在第二次遍历中，我们需要获取的是 <code>LC_SEGMENT(__DATA)</code> 中 <code>__nl_symbol_ptr</code> 和 <code>__la_symbol_ptr</code> 这两个 <em>Section</em>。其目的是为了确定 <strong>lazy binding指针表</strong> 和 <strong>non lazy binding指针表</strong> 在 <em>Dynamic Symbol</em> 中对应的位置，方法就是获取到 <em>reserved1</em> 字段。这个在后面我们会做一个验证实现。</p>
<h3 id="section-5">重绑定</h3>
<p>在一切基址准备好之后，开始进行重绑定 <code>perform_rebinding_with_section</code> 方法：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">static</span> <span class="n">void</span> <span class="n">perform_rebinding_with_section</span><span class="p">(</span><span class="n">struct</span> <span class="n">rebindings_entry</span> <span class="o">*</span><span class="n">rebindings</span><span class="p">,</span>
                                           <span class="n">section_t</span> <span class="o">*</span><span class="n">section</span><span class="p">,</span>
                                           <span class="n">intptr_t</span> <span class="n">slide</span><span class="p">,</span>
                                           <span class="n">nlist_t</span> <span class="o">*</span><span class="n">symtab</span><span class="p">,</span>
                                           <span class="n">char</span> <span class="o">*</span><span class="n">strtab</span><span class="p">,</span>
                                           <span class="n">uint32_t</span> <span class="o">*</span><span class="n">indirect_symtab</span><span class="p">)</span> <span class="p">{</span>
    <span class="sr">//</span> <span class="err">在</span> <span class="no">Indirect</span> <span class="no">Symbol</span> <span class="err">表中检索到对应位置</span>
    <span class="n">uint32_t</span> <span class="o">*</span><span class="n">indirect_symbol_indices</span> <span class="o">=</span> <span class="n">indirect_symtab</span> <span class="o">+</span> <span class="n">section</span><span class="o">-&gt;</span><span class="n">reserved1</span><span class="p">;</span>
    <span class="sr">//</span> <span class="err">获取</span> <span class="n">_DATA</span><span class="o">.</span><span class="n">__nl_symbol_ptr</span><span class="p">(</span><span class="err">或</span><span class="n">__la_symbol_ptr</span><span class="p">)</span> <span class="no">Section</span>
    <span class="sr">//</span> <span class="err">已知其</span> <span class="n">value</span> <span class="err">是一个指针类型，整段区域用二阶指针来获取</span>
    <span class="n">void</span> <span class="o">**</span><span class="n">indirect_symbol_bindings</span> <span class="o">=</span> <span class="p">(</span><span class="n">void</span> <span class="o">**</span><span class="p">)((</span><span class="n">uintptr_t</span><span class="p">)</span><span class="n">slide</span> <span class="o">+</span> <span class="n">section</span><span class="o">-&gt;</span><span class="n">addr</span><span class="p">);</span>
    <span class="sr">//</span> <span class="err">用</span> <span class="n">size</span> <span class="o">/</span> <span class="err">一阶指针来计算个数，遍历整个</span> <span class="no">Section</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">uint</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">section</span><span class="o">-&gt;</span><span class="n">size</span> <span class="o">/</span> <span class="n">sizeof</span><span class="p">(</span><span class="n">void</span> <span class="o">*</span><span class="p">);</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="sr">//</span> <span class="err">通过下标来获取每一个</span> <span class="no">Indirect</span> <span class="no">Address</span> <span class="err">的</span> <span class="no">Value</span>
        <span class="sr">//</span> <span class="err">这个</span> <span class="no">Value</span> <span class="err">也是外层寻址时需要的下标</span>
        <span class="n">uint32_t</span> <span class="n">symtab_index</span> <span class="o">=</span> <span class="n">indirect_symbol_indices</span><span class="o">[</span><span class="n">i</span><span class="o">]</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">symtab_index</span> <span class="o">==</span> <span class="no">INDIRECT_SYMBOL_ABS</span> <span class="o">||</span> <span class="n">symtab_index</span> <span class="o">==</span> <span class="no">INDIRECT_SYMBOL_LOCAL</span> <span class="o">||</span>
            <span class="n">symtab_index</span> <span class="o">==</span> <span class="p">(</span><span class="no">INDIRECT_SYMBOL_LOCAL</span>   <span class="o">|</span> <span class="no">INDIRECT_SYMBOL_ABS</span><span class="p">))</span> <span class="p">{</span>
            <span class="n">continue</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="sr">//</span> <span class="err">获取符号名在字符表中的偏移地址</span>
        <span class="n">uint32_t</span> <span class="n">strtab_offset</span> <span class="o">=</span> <span class="n">symtab</span><span class="o">[</span><span class="n">symtab_index</span><span class="o">].</span><span class="n">n_un</span><span class="o">.</span><span class="n">n_strx</span><span class="p">;</span>
        <span class="sr">//</span> <span class="err">获取符号名</span>
        <span class="n">char</span> <span class="o">*</span><span class="n">symbol_name</span> <span class="o">=</span> <span class="n">strtab</span> <span class="o">+</span> <span class="n">strtab_offset</span><span class="p">;</span>
        <span class="sr">//</span> <span class="err">过滤掉符号名小于</span> <span class="mi">4</span> <span class="err">位的符号</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">strnlen</span><span class="p">(</span><span class="n">symbol_name</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span> <span class="o">&lt;</span> <span class="mi">2</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">continue</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="sr">//</span> <span class="err">取出</span> <span class="n">rebindings</span> <span class="err">结构体实例数组，开始遍历链表</span>
        <span class="n">struct</span> <span class="n">rebindings_entry</span> <span class="o">*</span><span class="n">cur</span> <span class="o">=</span> <span class="n">rebindings</span><span class="p">;</span>
        <span class="k">while</span> <span class="p">(</span><span class="n">cur</span><span class="p">)</span> <span class="p">{</span>
            <span class="sr">//</span> <span class="err">对于链表中每一个</span> <span class="n">rebindings</span> <span class="err">数组的每一个</span> <span class="n">rebinding</span> <span class="err">实例</span>
            <span class="sr">//</span> <span class="err">依次在</span> <span class="nb">String</span> <span class="no">Table</span> <span class="err">匹配符号名</span>
            <span class="k">for</span> <span class="p">(</span><span class="n">uint</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">j</span> <span class="o">&lt;</span> <span class="n">cur</span><span class="o">-&gt;</span><span class="n">rebindings_nel</span><span class="p">;</span> <span class="n">j</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
                <span class="sr">//</span> <span class="err">符号名与方法名匹配</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="o">&amp;</span><span class="n">symbol_name</span><span class="o">[</span><span class="mi">1</span><span class="o">]</span><span class="p">,</span> <span class="n">cur</span><span class="o">-&gt;</span><span class="n">rebindings</span><span class="o">[</span><span class="n">j</span><span class="o">].</span><span class="n">name</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
                    <span class="sr">//</span> <span class="err">如果是第一次对跳转地址进行重写</span>
                    <span class="k">if</span> <span class="p">(</span><span class="n">cur</span><span class="o">-&gt;</span><span class="n">rebindings</span><span class="o">[</span><span class="n">j</span><span class="o">].</span><span class="n">replaced</span> <span class="o">!=</span> <span class="no">NULL</span> <span class="o">&amp;&amp;</span>
                        <span class="n">indirect_symbol_bindings</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">!=</span> <span class="n">cur</span><span class="o">-&gt;</span><span class="n">rebindings</span><span class="o">[</span><span class="n">j</span><span class="o">].</span><span class="n">replacement</span><span class="p">)</span> <span class="p">{</span>
                        <span class="sr">//</span> <span class="err">记录原始跳转地址</span>
                        <span class="o">*</span><span class="p">(</span><span class="n">cur</span><span class="o">-&gt;</span><span class="n">rebindings</span><span class="o">[</span><span class="n">j</span><span class="o">].</span><span class="n">replaced</span><span class="p">)</span> <span class="o">=</span> <span class="n">indirect_symbol_bindings</span><span class="o">[</span><span class="n">i</span><span class="o">]</span><span class="p">;</span>
                    <span class="p">}</span>
                    <span class="sr">//</span> <span class="err">重写跳转地址</span>
                    <span class="n">indirect_symbol_bindings</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="n">cur</span><span class="o">-&gt;</span><span class="n">rebindings</span><span class="o">[</span><span class="n">j</span><span class="o">].</span><span class="n">replacement</span><span class="p">;</span>
                    <span class="sr">//</span> <span class="err">完成后不再对当前</span> <span class="no">Indirect</span> <span class="no">Symbol</span> <span class="err">处理</span>
                    <span class="sr">//</span> <span class="err">继续迭代到下一个</span> <span class="no">Indirect</span> <span class="no">Symbol</span>
                    <span class="n">goto</span> <span class="n">symbol_loop</span><span class="p">;</span>
                <span class="p">}</span>
            <span class="p">}</span>
            <span class="sr">//</span> <span class="err">链表遍历</span>
            <span class="n">cur</span> <span class="o">=</span> <span class="n">cur</span><span class="o">-&gt;</span><span class="k">next</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="ss">symbol_loop</span><span class="p">:;</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></div>
<p>这段方法主要描述了替换 <code>__DATA.__la_symbol_ptr</code> 和 <code>__DATA.__la_symbol_ptr</code> 的 <em>Indirect Pointer</em> 主要过程。从 <code>reserved1</code> 字段获取到 <em>Indirect Symbols</em> 对应的位置。从中我们可以获取到指定符号的偏移量，这个偏移量主要用来在 String Table 中检索出符号名称字符串。之后我们找到 <code>__DATA.__la_symbol_ptr</code> 和 <code>__DATA.__la_symbol_ptr</code> 这两个 Section。这两个表中，都是由 <em>Indirect Pointer</em> 构成的指针数组，但是<strong>其中的元素决定了我们调用的方法应该以哪个代码段的方法来执行</strong>。我们遍历这个指针数组中每一个指针，在每一层遍历中取出其符号名称，与我们的 <code>rebindings</code> 链表中每一个元素进行比对，当名称匹配的时候，重写其指向地址。</p>
<p><img src="../assets/images/blog/15101394649922/15134988564426.jpg" alt="" /></p>
<h2 id="section-6">小结</h2>
<p>以上就是 <em>fishhook</em> 的全部源码实现。本文完成了对于 <em>fishhook</em> 全部运行时流程进行了详尽的分析。在 <a href="">验证试验 - 探求 fishhook 原理（二）</a> 中，将会对 <em>fishhook</em> 的全部流程以计算的方式进行模拟实践。并对 <em>fishhook</em> 代码中的一些处理和技巧做出一一剖析。尽请期待。</p>
<h2 id="section-7">引文</h2>
<ul>
  <li><a href="http://linuxtools-rst.readthedocs.io/zh_CN/latest/">Linux Tools Quick Tutorial</a></li>
  <li><a href="https://amywushu.github.io/2017/02/27/%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0-Hook-%E5%8E%9F%E7%90%86%E4%B9%8B-fishhook-%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90.html">Hook 原理之 fishhook 源码解析</a></li>
</ul>
<h2 id="section-8">鸣谢</h2>
<p>感谢 <a href="http://www.alonemonkey.com/">AloneMonkey</a> 和 <a href="http://jmpews.github.io/">jmpews</a> 两位大牛对我的一些帮助。</p>
  <footer class="post-footer">
    <section class="author">
      <h4>Desgard_Duan</h4>
      <p>I write many code to write less code.💻</p>
    </section>
<aside class="share">
  <h4>Share this.</h4>
  <a href="http://twitter.com/share?text=《巧用符号表 - 探求 fishhook 原理（一）》 -- Guardia · 瓜地&amp;url=https://www.desgard.com/fishhook-1/"
  onclick="window.open(this.href, 'twitter-share', 'width=550,height=235');return false;">
    <i class="fa fa-twitter-square"></i>
  </a>
  <a href="http://v.t.sina.com.cn/share/share.php?url=https://www.desgard.com/fishhook-1/&amp;title=《巧用符号表 - 探求 fishhook 原理（一）》 —— Guardia · 瓜地" onclick="window.open(this.href, 'twitter-share', 'width=550,height=235');return false;">
    <i class="fa fa-weibo"></i>
  </a>
</aside>
      <hr>
<aside id="comments" class="disqus">
  <div id="container"></div>
  <!-- <link rel="stylesheet" href="/assets/css/gitment.css">
  <script src="https://imsun.github.io/gitment/dist/gitment.browser.js"></script>
  <script>
  var gitment = new Gitment({
    id: "https://www.desgard.com/fishhook-1/", 
    owner: 'Desgard',
    repo: 'desgard.github.com',
    oauth: {
      client_id: 'e2612df42f3f2a83e71c',
      client_secret: 'b53e85b314bb24a6d06773e48bbb62a4de3b8b3a',
    },
  })
  gitment.render('container')
  </script> -->
<link rel="stylesheet" href="/assets/css/gitalk.css">
<script src="https://unpkg.com/gitalk/dist/gitalk.min.js"></script>
<div id='gitalk-container'></div>
<script>
    const gitalk = new Gitalk({
        id: "https://www.desgard.com/fishhook-1/", 
        clientID: 'e2612df42f3f2a83e71c',
        clientSecret: 'b53e85b314bb24a6d06773e48bbb62a4de3b8b3a',
        repo: 'desgard.github.com',
        owner: 'Desgard',
        admin: ['Desgard'],
        // facebook-like distraction free mode
        distractionFreeMode: false
    })
    gitalk.render('gitalk-container')
</script>
</aside>
  </footer>
</article>
    </main>
<footer class="footer">
  <div class="container">
    <ul class="icons">
      <li>
        <a href="https://github.com/desgard" class="icon-github" target="_blank">
          <i class="fa fa-github"></i>
        </a>
      </li>
      <li>
        <a href="https://www.facebook.com/desgard.duan" class="icon-facebook" target="_blank">
          <i class="fa fa-facebook"></i>
        </a>
      </li>
      <li>
        <a href="https://twitter.com/Desgard_Duan" class="icon-twitter" target="_blank">
          <i class="fa fa-twitter"></i>
        </a>
      </li>
      <li>
        <a href="https://stackoverflow.com/users/6119149/desgard-duan" class="icon-github" target="_blank">
          <i class="fa fa-stack-overflow"></i>
        </a>
      </li>
      <li>
        <a href="https://weibo.com/desgard" class="icon-instagram" target="_blank">
          <i class="fa fa-weibo"></i>
        </a>
      </li>
    </ul>
    <p>
      <q>I write many code to write less code.💻</q>
      <small>– Gua</small>
    </p>
    <small class="clearfix">
      Powered by <a href="http://jekyllrb.com" target="_blank">Jekyll</a> • <a href="https://github.com/desgard" target="_blank">Open source <i class="fa fa-heart"></i></a>
    </small>
  </div>
</footer>
<a class="scroll-up fa fa-chevron-up bounce" href="#" data-position="0"></a>
<div id="modalSearch" class="modal">
  <div class="modal__overlay"></div>
  <div class="modal__content">
    <a href="#!" class="modal-close" data-modal-close>&times;</a>
    <div class="search-container">
      <input type="text" id="search-input" placeholder="Search articles">
      <ul id="results-container"></ul>
    </div>
  </div>
</div>
    <script src="/assets/main-52d417e8a6ff9f5b168386d37c96338a.js"></script>
  </body>
  <script>
    var link = "http://m.desgard.com/2017/12/17/fishook-1/" ;
    var os = function() {  
      var ua = navigator.userAgent,  
      isWindowsPhone = /(?:Windows Phone)/.test(ua),  
      isSymbian = /(?:SymbianOS)/.test(ua) || isWindowsPhone,   
      isAndroid = /(?:Android)/.test(ua),   
      isFireFox = /(?:Firefox)/.test(ua),   
      isChrome = /(?:Chrome|CriOS)/.test(ua),  
      isTablet = /(?:iPad|PlayBook)/.test(ua) || (isAndroid && !/(?:Mobile)/.test(ua)) || (isFireFox && /(?:Tablet)/.test(ua)),  
      isPhone = /(?:iPhone)/.test(ua) && !isTablet,  
      isPc = !isPhone && !isAndroid && !isSymbian;  
      return {  
        isTablet: isTablet,  
        isPhone: isPhone,  
        isAndroid : isAndroid,  
        isPc : isPc  
      };  
    }();  
    if (link.length > 0) {
      if (os.isAndroid || os.isPhone) {
        location.replace(link);
      }
    }
  </script>
</html>